home *** CD-ROM | disk | FTP | other *** search
-
- Debugging Amiga Software
-
- by Carolyn Scheppner
-
- This article presents some general techniques for debugging software on the
- Amiga. Before you start programming the Amiga, it's a good idea to read the
- development guidelines in the introductions of the the Addison-Wesley Hardware
- Manual (ISBN 0-201-18157-6) and ROM Kernel Reference Manual: Libraries and
- Devices (ISBN 0-201-18187-8). These guidelines contain important rules
- which are applicable to all Amiga programs, configurations, and operating
- system releases. Additional information can be found in the Troubleshooting
- Guide published in Amiga Mail and in the Libraries and Devices manual.
- These documents cover the most common Amiga programming problems.
-
- Preventing Bugs
-
- The best way to debug software is to prevent bugs in the first place.
- Accordingly, here are seven basic rules you should always follow when
- writing Amiga software:
-
- 1. Use Enforcer abd Mungwall while developing your code.
-
- 2. Read the latest autodocs and the include file comments for the functions
- and structures you are using.
-
- 3. Always check return values from system functions. Provide a clean way
- out and useful messages if something fails! Assembler programmers -
- remember to TST.L D0 after system calls, before branching on condition codes.
-
- 4. C Programmers - Use function prototypes for system functions and your own
- functions. It's a little extra work but will save you time in the long run
- by immediately catching most types of improper function calls (missing
- arguments, swapped args, etc.)
-
- 5. Keep a version number in your code and update the version number whenever
- changes are made. The 2.0 VERSION command can print out the version
- of any executable which contains a specially formatted version string:
-
- In C:UBYTE *vers="\0$VER: programname 36.10";
- In Asm:vers DC.B 0,'$VER: programname 36.10',0
-
-
- 6. Document the code changes for each version. This can be done manually
- or by using a document control system such as RCS.
-
- 7. Test your code! Test on different configurations, under low memory
- and error conditions, and in conjunction with various watchdog tools.
- Test your product with MungWall, if possible in conjunction with Enforcer
- to catch uses of null pointers and freed memory.
-
-
- Finding and Fixing Bugs
-
- It is hard to generalize about debugging because different kinds of bugs
- often require very different approaches. A bug report from a user is quite
- different from a bug that you've just introduced in new code! However, all
- debugging requires some common steps:
-
- 1. Define the problem
- 2. Narrow the search and find the bug
- 3. Understand, and fix the bug
- 4. Make sure you didn't just break something else
-
- Steps 3 and 4 are the same for all types of bugs, so we'll cover those last.
- Steps 1 and 2 require different approaches for different kinds of bugs.
- Here are some examples.
-
- You've added or written new code and something is broken.
-
- 1. Define the problem. Make sure you can reproduce the problem so you'll
- know when it's gone. Define as "when I do xxx the program does (or doesn't
- do) do yyy."
-
- 2. Narrow the Search. If you just added a couple of lines of code, and have
- the same development environment as before, check your source code first.
- Check for misuse of existing variables, improper error checking, improper
- use of system or internal functions, and possible changes to conditional
- program flow.
-
- If you can't spot the problem, it's time to slow it down and see what's going
- on. Use a source-level or symbolic debugger, or print/kprint/dprint
- debugging, with delays added if necessary. One particularly useful type of
- debugging statement is:
-
- printf("About to do xxx. k =%ld Ptr1=$%lx...\n",k,Ptr1);
- Delay(50);
-
- The delay gives the debugging line time to be output and gives you a chance
- to read it before the action is taken. See mydebug.h on the 1991 DevCon
- disks for easy ways to add conditional debugging statements like this to
- your code.
-
- By stepping through or printing out your actions and variables, you will
- generally be able to isolate the bug. If you have isolated the area but
- still can't find the bug, re-read the autodocs for the routines you are
- using. Check the Troubleshooting Guide in the Addison-Wesley Libraries and
- Devices manual. Check all other uses of the variables in the problem area.
- If all else fails, isolate the problem code by writing the smallest possible
- example that demonstrates the problem.
-
- If the problem is not present in the smallest possible example, then go back
- and check your code. If the problem is still present, contact CATS for
- assistance or upload the example to BIX (note - one of the quickest ways to
- find bugs in a small source code example is to upload the source to BIX
- amiga.dev/main and ask what's wrong with it).
-
-
- B. Your code has intermittent problems that you can't pin down, or appears to
- trash something under certain conditions.
-
- 1. Define the problem. It is difficult to reproduce intermittent problems,
- so try to force the problem to show itself. First try running your program
- with Enforcer and MungWall. If you don't have an MMU,
- use WatchMem and MungWall, but be prepared to crash a lot. If you don't get
- any hits, try the same thing during low-memory situations, heavy multitasking
- and device IO, etc. If you are doing Exec device IO, try IO_Torture to catch
- premature reuse of IORequests. Hopefully, you will pick up a hit.
-
- 2. Narrow the Search. If you have no MungWall/Enforcer hits, try some
- debugging statements or source-level debugging to follow the values of your
- variables. Use TStat to see if your stack usage is high. Check all possible
- areas where you might be overwriting the end of an array or otherwise
- trashing memory. Re-read the autodocs for the system functions you are using.
-
- If you are reusing an IORequest too soon, check your source code (debugging
- would just slow down your execution and might give the IORequest a chance to
- complete, masking the problem).
-
- If you have Enforcer hits, use debugging statements or a debugger to step
- through your code WHILE running MungWall and Enforcer (or WatchMem).
- This will allow you to pinpoint where the problem occurs.
-
-
-
- C. Your code works fine on one system but not on another. Or you've received
- a bug report from a user.
-
- 1. Define the problem. First find out the exact configuration of the system
- the problem occurred on. Important elements include memory configuration and
- addresses, amount of free Chip and Fast RAM, processor type, custom chip
- version, expansion peripherals, OS version, and other software in use when
- the problem occurred. The Config program on the 1991 DevCon disks is useful
- for printing out much of this information.
-
- The memory address ranges can be particularly important now that machines are
- available with memory beyond the 24 bit address limit. For example,
- overwriting a byte array by one byte now has a good chance of trashing a
- 32-bit address variable, or even your routine's return address on the stack.
-
- If a user reports the problem, find out the exact version of your software
- they are running, how they launched the program, and what their stack is set
- to (if launched from CLI). Try to get them to reproduce the problem in a
- known environment (ie. after booting with a release Workbench diskette).
- Get their phone number and keep it with a record of all of the information
- you can get on the problem. Keep bug reports in an organized form. If you
- get two reports on the same problem, you can be pretty sure that the problem
- really exists, and the combined information may help you track it down.
-
- 2. Narrow the Search. Attempt to reproduce the problem. If you can't
- reproduce it immediately, try stepping through the problem area while using
- Enforcer and MungWall. If you don't get any hits, try again with less
- memory available and other tasks running. Try to reproduce the user's
- configuration and environment. If you still can not reproduce the problem,
- ask the user to come up with a simple repeatable sequence which causes the
- problem on a system booted with a normal release Workbench disk.
-
- Read the Troubleshooting guide in the Addison-Wesley Libraries and Devices
- manual or Amiga Mail Technotes for information on the causes for many
- problems that only show up in certain configurations or environments.
-
- If all else fails, look carefully at your code for misuse of variables or
- system functions, and for improper error-checking or cleanup after any
- allocation or open. Check that all cleanups are done in the proper order.
-
-
-
-
- D. Your program loses memory.
-
- 1. Define the problem. First make sure that you are actually losing memory.
- Use Flush (from the 1991 DevCon disks) and Avail to check for actual memory
- loss.
-
- Set up your system so you have a shell window available and can start your
- program without moving any windows (re-arranging windows causes memory
- fluctuations). Test for memory loss as follows. First, try Flush and Avail
- a few times to make sure nothing else in your system is causing memory to
- fluctuate. Then perform the following steps.
-
- 1. FLUSH
- 2. AVAIL (write down the Fast, Chip, and total memory free)
- 3. Start your program and use its features
- 4. Exit your program
- 5. FLUSH
- 6. AVAIL (compare the fast, chip, and total free to previous figures)
- 7. If you have a loss, go back to step 2.
-
- Testing such as shown above will flush out all properly closed devices,
- libraries, and fonts which have been loaded from disk by your program and
- other programs. This allows you to check for actual memory loss.
-
- Note that under 2.0, since the audio.device is ROM-resident but
- not initialized by the system until it is opened by someone, the
- first program to use the audio device or speech capabilities will
- appear to cause a small but permanent memory loss. This is the
- memory allocated for the audio device's base structures. If your program
- uses audio or speech, first use the SAY program or SPEECH: before performing
- the above memory loss test so that the audio device's initial memory
- usage will not interfere with your tests.
-
- One special memory loss problems is a continual loss of memory while
- a program is running. This is generally caused by not keeping up
- with IntuiMessages, or not freeing Locks.
-
- 2. Narrow the search. Try the above test again, but this time just start
- your program and exit immediately. If you do not lose memory, try several
- times more, using some of your program's features, and attempt to determine
- which part of your program causes the memory loss. Check your source code
- for all opens and allocations and check for matching frees and closes, in
- the proper order, for each of them.
-
-
-
- The size of a memory loss can also be a clue to the cause. For example, a
- loss of exactly 24 bytes is probably a Lock() which has not been UnLock()'d.
- Knowing the exact size of the loss (as determined with Flush and Avail) is
- important when you try determine which allocation is not being freed.
-
- Some additional tools on the 1991 DevCon disks can help determine where
- memory losses occur. You can use MemMon to record the relative memory usage
- as you test various parts of your program. Snoop can be used to record all
- memory allocations and frees on a remote terminal, after which SnoopStrip
- can strip out all matching pairs. MungWall contains an enhanced snoop
- option for tracking only the memory allocations of a particular task.
- MemList, which outputs the system memory list, can also be useful when
- debugging memory loss and fragmentation.
-
- The Wedge program, which can restrict its reporting to the function calls
- made by a single task or list of tasks, can also be used to monitor the
- allocations and frees done by your task. By inserting debugging statements,
- you can mix status messages ("About to do xxx") with Wedge or MungWall
- SNOOP output. Examine the output for an allocation which matches the size
- of your loss. Use LVO's WEDGELINE option to generate command lines
- for Wedge.
-
-
- Removing Bugs
-
- I mentioned steps 3 and 4 earlier. This is the easy part (finding the bug is
- the hard part). These steps are the same for most debugging problems.
-
- 3. Understand, and Fix the Bug. When you find the bug, make sure you
- understand it. Don't just try something else. If you are having a problem
- with a system routine, read the autodocs and chapter text for that routine.
- Check the Troubleshooting Guide in the 1.3 Addison- Wesley Libraries and
- Devices manual.
-
- When you understand what is wrong, fix the problem, being especially careful
- not to affect the behavior of any other parts of your program. Carefully
- document the changes that you make and bump the revision number of the
- program. Note your changes in the initial comments of the program, and in
- the area where the changes were made.
-
-
- 4. Make Sure You Didn't Break Anything New. Try to reproduce the problem
- several times and make sure it is gone. Thoroughly test the rest of your
- program and make sure that nothing else has been broken by your fix. Test
- your program in combination with watchdog tools such as MungWall and Enforcer.
-
-
-
-
- Debugging Tools
-
- 1. Software Watchdog and Stressing Tools
-
- The MMU-based Amiga debugging tool "Enforcer" provides debugging and quality
- assurance capabilities far beyond what was previously possible. It is now
- possible to find bugs even in code that appears to be working perfectly -
- the kinds of bugs that could cause serious problems on different
- configurations. Enforcer is able to trap improper low memory accesses,
- writes to ROM, and accesses of non-existent memory - problems which are
- generally caused by use of freed or improperly initialized pointers or
- structures.
-
- When used in conjunction with a free memory invalidation tool such
- as MungWall, additional illegal memory uses are forced out into
- areas trappable by Enforcer.
-
- Another extremely useful testing tool, especially for assembler programmers,
- is "Scratch" by Bill Hawes. One of the most surprising compatibility
- problems we have seen is improper use or dependence on scratch registers
- (D1,A0,A1) after a system call. Scratch allows you to invalidate the
- contents of these scratch registers after system calls so that improper
- usage of these registers in your code may be brought out.
-
- It is also useful to test your software with stressing tools such
- as EatMem, Memoration, and EatCycles, in conjunction with Enforcer
- because Enforcer will help to catch use of unsuccessful allocations
- immediately.
-
- All software should be tested with these tools during development, and
- should be required to pass a test with Enforcer in conjunction with
- MungWall, Scratcher, and IO_Torture before being released and distributed.
-
-
- 2. Symbolic and source-level debuggers
-
- Symbolic debuggers allow you to trace and single step through your code, and
- examine or change your variables and structures. The source-level debuggers
- which are provided with some compilers allow you to trace and single step
- your code at the source-level after compiling with special flags. Debuggers
- can often be used in combination with other tools such as MungWall and
- Enforcer to detect exactly where a problem is occurring.
-
- 3. Printf() and kprintf() / dprintf() debugging
-
- This simple method of debugging allows you to monitor where you are, what
- your variables contain, and anything else you care to print out. Printf
- debugging is suitable for any process code that is not in a Forbid or
- Disable (printf breaks a Forbid or Disable). Kprintf (serial) and dprintf
- (parallel) debugging is more flexible and can be used in process, task, or
- interrupt code. The kprintf function is provided in the debug.lib linker
- library. The parallel version, dprintf, is provided in the ddebug.lib
- linker library. See the debug.lib kprintf autodocs for more information on
- the types of formats handled by kprintf and dprintf.
-
- Kprintf outputs to the serial port at whatever baud rate the port is
- currently set to. Generally, kprintf is done at 9600 baud with a terminal,
- or another Amiga running a terminal package, connected to your serial port
- with a null modem serial cable.
-
-
-
- However, it is possible to kprintf to yourself (ie. to a terminal package
- running on your own machine) if you have a modem attached to your serial
- port, and your terminal package set to the baud rate of your modem.
- Obviously, if the problem you are debugging causes you to crash, a remote
- terminal is a better choice. The ASCII capture feature of your terminal
- package can be used to capture the kprintf debugging output for later
- examination.
-
- Remote (kprintf/dprintf) debugging is extremely useful when combined with
- other remote debugging tools such as Enforcer and IO_Torture because your own
- debugging statements will be interspersed with the remote output of the
- other debugging tools, allowing you to track what your program is doing
- when problems occur.
-
- Printf/kprintf/dprint debugging can be conditionally coded more conveniently
- by using an include file such as mydebug.h (see the DevCon disks). Mydebug.h
- eliminates the need for messy #ifdef and #endif lines around your debugging
- statements by providing the conditional macros D(bug()), D2(bug()), and
- DQ(bug()) which take printf-style format strings and arguments in their
- inner parens. One handy feature of these macros is that your debugging
- statements can be be quickly changed from printf's to kprintf's or dprintf's
- by simply setting a flag in mydebug.h and recompiling.
-
- Example: D(bug("I'm here now and a=%ld",a));
-
- 4. Other ways to debug low -level code
-
- If you can't link with debug.lib, low level code can also be debugged by
- inserting visual or audio cues to let you know where you are. DebTones.asm
- (in AmigaMail and on the 1991 DevCon disks) demonstrates a small audio tone
- macro suitable for debugging low level code. Another common method is
- flashing the power LED (see togl_led.asm), or doing an Intuition DisplayBeep()
- to flash the screen.
-
- 5. Specialized debugging tools
-
- A variety of specialized debugging tools are available for monitoring and
- debugging such things as system function calls, device IO, process status,
- memory usage, and software errors. These tools can be used without
- recompiling your program and can provide valuable debugging information.
- See the list of tools accompanying this article in the DevCon notes.
-
-
-
- How to Use MMU Watchdogs and Other Remote Debugging Tools
-
- Remote Serial Debugging
-
- Hardware Setup: When hooking two Amigas together, use a straight RS-232
- cable with a null-modem adaptor, or use a null-modem cable. When hooking
- an Amiga up to another type of computer or terminal, you may or may not need
- the null-modem (crossed lines) depending on whether the other machine's
- RS-232 port is designed to be basically a sender or a receiver. Avoid
- connecting lines which are not directly related to RS-232 because different
- computers have various power supplies and grounds on these other lines. My
- null modem debugging cable is wired as follows:
-
- Amiga Terminal
- 1 1
- 2 2
- 3 3
- 7 7
- 5, 6, 8 5, 6, 8
- 20 20
-
- Software: For remote debugging at 9600 baud, set the sending machine's
- Preferences to 9600 baud, and use a 9600 baud terminal or an Amiga running
- a 9600 baud terminal package (preferably with ASCII capture capability) as
- the receiving machine. Note that other baud rates can also be used for most
- serial debugging because normal serial kprintf's do not modify the serial
- SERPER register and are therefore output at the last baud rate your serial
- hardware was set to. Test your setup by copying a small text file to SER:
- or try the ktest program from the 1900 DevCon disks. The output should show
- up on the remote terminal.
-
- Applications can output serial debugging statements by using kprintf from C
- or KPrintF from assembler and linking with amiga.lib and debug.lib. See the
- debug.lib autodocs for more information. Serial input functions are also
- available. Also see the mydebug.h conditional debugging macros on the 1991
- DevCon disks.
-
- Watchdog software setup: Make sure your test machine is set to the same
- baud rate as the remote terminal you are connected to. Turn on the ASCII
- capture of your remote terminal.
-
-
- Serial Watchdog software setup:
-
- For machines with 68030 or 68020+MMU:
- [RUN] MungWall (removable with CTRL-C, or BREAK n if RUN)
- [RUN] IO_Torture
- Enforcer ON
-
- For non-MMU machines (warning - encourages bad software to crash!)
- [RUN] MungWall (removable with CTRL-C, or BREAK n if RUN)
- [RUN] IO_Torture
- [RUN] WatchMem
-
-
- Setup for Local Serial Debugging
-
- Hardware: If you have a modem attached to your serial port, it is possible
- to capture your own serial debugging output locally. This setup can be
- useful as long as the problem you are debugging is not one which crashes
- the machine.
-
- Software: Run a terminal package at your modem's baud rate to capture the
- kprintfs. You probably won't be able to test this setup by copying a file
- to SER: (since the terminal package probably has an exclusive open on the
- serial device). Instead, use a small program like ktest (on the 1991 DevCon
- disks) to test your setup, or, if you already have an MMU watchdog installed,
- try an illegal memory accessor such as Lawbreaker. Use the terminal package's
- ASCII capture feature to capture your debugging output.
-
- Watchdog software setup: Same as for remote serial debugging, but first
- start up a terminal package on the test machine, at the baud rate of the
- attached modem, with ASCII capture turned on.
-
-
- Setup for Parallel Debugging
-
- Hardware: To set up for parallel debugging, attach a parallel printer to
- the Amiga's parallel port and turn the printer on. Note - if no device is
- attached to the parallel port, parallel debugging statements will hang
- waiting for the port hardware.
-
- Software: Some debugging commands have options for parallel rather than
- serial output. Examples include Enforcer.par, MungWall.par, IO_Torture.par,
- and Wedge with the 'p' option. Also, your can send your own debugging
- statements to the parallel port by using dprintf from C, or DPutFmt from
- assembler, and linking with ddebug.lib and amiga.lib. On the 1991 DevCon
- disks, see dtest.asm for an example of calling DPutFmt from assembler, and
- mydebug.h for debugging macros which can use printf, kprintf, or dprintf.
-
-
- Parallel Watchdog software setup:
-
- For machines with 68030 or 68020+MMU:
- [RUN] MungWall.par (removable with CTRL-C, or BREAK n if RUN)
- [RUN] IO_Torture.par
- Enforcer.par ON
-
- For non-MMU machines (warning - encourages bad software to crash!)
- [RUN] MungWall.par (removable with CTRL-C, or BREAK n if RUN)
- [RUN] IO_Torture.par
- [RUN] WatchMem
-
-